/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of XreaL source code.

XreaL source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

XreaL source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with XreaL source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// cmd.c -- Quake script command processing module

#include "q_shared.h"
#include "qcommon.h"

#define	MAX_CMD_BUFFER	16384
#define	MAX_CMD_LINE	1024

typedef struct
{
	byte           *data;
	int             maxsize;
	int             cursize;
} cmd_t;

int             cmd_wait;
cmd_t           cmd_text;
byte            cmd_text_buf[MAX_CMD_BUFFER];


//=============================================================================

/*
============
Cmd_Wait_f

Causes execution of the remainder of the command buffer to be delayed until
next frame.  This allows commands like:
bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
============
*/
void Cmd_Wait_f(void)
{
	if(Cmd_Argc() == 2)
	{
		cmd_wait = atoi(Cmd_Argv(1));
		if(cmd_wait < 0)
			cmd_wait = 1;		// ignore the argument
	}
	else
	{
		cmd_wait = 1;
	}
}


/*
=============================================================================

						COMMAND BUFFER

=============================================================================
*/

/*
============
Cbuf_Init
============
*/
void Cbuf_Init(void)
{
	cmd_text.data = cmd_text_buf;
	cmd_text.maxsize = MAX_CMD_BUFFER;
	cmd_text.cursize = 0;
}

/*
============
Cbuf_AddText

Adds command text at the end of the buffer, does NOT add a final \n
============
*/
void Cbuf_AddText(const char *text)
{
	int             l;

	l = strlen(text);

	if(cmd_text.cursize + l >= cmd_text.maxsize)
	{
		Com_Printf("Cbuf_AddText: overflow\n");
		return;
	}
	Com_Memcpy(&cmd_text.data[cmd_text.cursize], text, l);
	cmd_text.cursize += l;
}


/*
============
Cbuf_InsertText

Adds command text immediately after the current command
Adds a \n to the text
============
*/
void Cbuf_InsertText(const char *text)
{
	int             len;
	int             i;

	len = strlen(text) + 1;
	if(len + cmd_text.cursize > cmd_text.maxsize)
	{
		Com_Printf("Cbuf_InsertText overflowed\n");
		return;
	}

	// move the existing command text
	for(i = cmd_text.cursize - 1; i >= 0; i--)
	{
		cmd_text.data[i + len] = cmd_text.data[i];
	}

	// copy the new text in
	Com_Memcpy(cmd_text.data, text, len - 1);

	// add a \n
	cmd_text.data[len - 1] = '\n';

	cmd_text.cursize += len;
}


/*
============
Cbuf_ExecuteText
============
*/
void Cbuf_ExecuteText(int exec_when, const char *text)
{
	switch (exec_when)
	{
		case EXEC_NOW:
			if(text && strlen(text) > 0)
			{
				Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text);
				Cmd_ExecuteString(text);
			}
			else
			{
				Cbuf_Execute();
				Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data);
			}
			break;
		case EXEC_INSERT:
			Cbuf_InsertText(text);
			break;
		case EXEC_APPEND:
			Cbuf_AddText(text);
			break;
		default:
			Com_Error(ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
	}
}

/*
============
Cbuf_Execute
============
*/
void Cbuf_Execute(void)
{
	int             i;
	char           *text;
	char            line[MAX_CMD_LINE];
	int             quotes;

	while(cmd_text.cursize)
	{
		if(cmd_wait > 0)
		{
			// skip out while text still remains in buffer, leaving it
			// for next frame
			cmd_wait--;
			break;
		}

		// find a \n or ; line break
		text = (char *)cmd_text.data;

		quotes = 0;
		for(i = 0; i < cmd_text.cursize; i++)
		{
			if(text[i] == '"')
				quotes++;
			if(!(quotes & 1) && text[i] == ';')
				break;			// don't break if inside a quoted string
			if(text[i] == '\n' || text[i] == '\r')
				break;
		}

		if(i >= (MAX_CMD_LINE - 1))
		{
			i = MAX_CMD_LINE - 1;
		}

		Com_Memcpy(line, text, i);
		line[i] = 0;

// delete the text from the command buffer and move remaining commands down
// this is necessary because commands (exec) can insert data at the
// beginning of the text buffer

		if(i == cmd_text.cursize)
			cmd_text.cursize = 0;
		else
		{
			i++;
			cmd_text.cursize -= i;
			memmove(text, text + i, cmd_text.cursize);
		}

// execute the command line

		Cmd_ExecuteString(line);
	}
}


/*
==============================================================================

						SCRIPT COMMANDS

==============================================================================
*/


/*
===============
Cmd_Exec_f
===============
*/
void Cmd_Exec_f(void)
{
	union
	{
		char           *c;
		void           *v;
	} f;
	int             len;
	char            filename[MAX_QPATH];

	if(Cmd_Argc() != 2)
	{
		Com_Printf("exec <filename> : execute a script file\n");
		return;
	}

	Q_strncpyz(filename, Cmd_Argv(1), sizeof(filename));
	Com_DefaultExtension(filename, sizeof(filename), ".cfg");
	len = FS_ReadFile(filename, &f.v);
	if(!f.c)
	{
		Com_Printf("couldn't exec %s\n", Cmd_Argv(1));
		return;
	}
	Com_Printf("execing %s\n", Cmd_Argv(1));

	Cbuf_InsertText(f.c);

	FS_FreeFile(f.v);
}


/*
===============
Cmd_Vstr_f

Inserts the current value of a variable as command text
===============
*/
void Cmd_Vstr_f(void)
{
	char           *v;

	if(Cmd_Argc() != 2)
	{
		Com_Printf("vstr <variablename> : execute a variable command\n");
		return;
	}

	v = Cvar_VariableString(Cmd_Argv(1));
	Cbuf_InsertText(va("%s\n", v));
}


/*
===============
Cmd_Echo_f

Just prints the rest of the line to the console
===============
*/
void Cmd_Echo_f(void)
{
	Com_Printf("%s\n", Cmd_Args());
}


/*
=============================================================================

					COMMAND EXECUTION

=============================================================================
*/

typedef struct cmd_function_s
{
	struct cmd_function_s *next;
	char           *name;
	xcommand_t      function;
	completionFunc_t complete;
} cmd_function_t;


static int      cmd_argc;
static char    *cmd_argv[MAX_STRING_TOKENS];	// points into cmd_tokenized
static char     cmd_tokenized[BIG_INFO_STRING + MAX_STRING_TOKENS];	// will have 0 bytes inserted
static char     cmd_cmd[BIG_INFO_STRING];	// the original command we received (no token processing)

static cmd_function_t *cmd_functions;	// possible commands to execute

/*
============
Cmd_Argc
============
*/
int Cmd_Argc(void)
{
	return cmd_argc;
}

/*
============
Cmd_Argv
============
*/
char           *Cmd_Argv(int arg)
{
	if((unsigned)arg >= cmd_argc)
	{
		return "";
	}
	return cmd_argv[arg];
}

/*
============
Cmd_ArgvBuffer

The interpreted versions use this because
they can't have pointers returned to them
============
*/
void Cmd_ArgvBuffer(int arg, char *buffer, int bufferLength)
{
	Q_strncpyz(buffer, Cmd_Argv(arg), bufferLength);
}


/*
============
Cmd_Args

Returns a single string containing argv(1) to argv(argc()-1)
============
*/
char           *Cmd_Args(void)
{
	static char     cmd_args[MAX_STRING_CHARS];
	int             i;

	cmd_args[0] = 0;
	for(i = 1; i < cmd_argc; i++)
	{
		strcat(cmd_args, cmd_argv[i]);
		if(i != cmd_argc - 1)
		{
			strcat(cmd_args, " ");
		}
	}

	return cmd_args;
}

/*
============
Cmd_Args

Returns a single string containing argv(arg) to argv(argc()-1)
============
*/
char           *Cmd_ArgsFrom(int arg)
{
	static char     cmd_args[BIG_INFO_STRING];
	int             i;

	cmd_args[0] = 0;
	if(arg < 0)
		arg = 0;
	for(i = arg; i < cmd_argc; i++)
	{
		strcat(cmd_args, cmd_argv[i]);
		if(i != cmd_argc - 1)
		{
			strcat(cmd_args, " ");
		}
	}

	return cmd_args;
}

/*
============
Cmd_ArgsBuffer

The interpreted versions use this because
they can't have pointers returned to them
============
*/
void Cmd_ArgsBuffer(char *buffer, int bufferLength)
{
	Q_strncpyz(buffer, Cmd_Args(), bufferLength);
}

/*
============
Cmd_Cmd

Retrieve the unmodified command string
For rcon use when you want to transmit without altering quoting
https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
============
*/
char           *Cmd_Cmd(void)
{
	return cmd_cmd;
}

/*
   Replace command separators with space to prevent interpretation
   This is a hack to protect buggy qvms
   https://bugzilla.icculus.org/show_bug.cgi?id=3593
*/
void Cmd_Args_Sanitize(void)
{
	int             i;

	for(i = 1; i < cmd_argc; i++)
	{
		char           *c = cmd_argv[i];

		while((c = strpbrk(c, "\n\r;")))
		{
			*c = ' ';
			++c;
		}
	}
}

/*
============
Cmd_TokenizeString

Parses the given string into command line tokens.
The text is copied to a seperate buffer and 0 characters
are inserted in the apropriate place, The argv array
will point into this temporary buffer.
============
*/
// NOTE TTimo define that to track tokenization issues
//#define TKN_DBG
static void Cmd_TokenizeString2(const char *text_in, qboolean ignoreQuotes)
{
	const char     *text;
	char           *textOut;

#ifdef TKN_DBG
	// FIXME TTimo blunt hook to try to find the tokenization of userinfo
	Com_DPrintf("Cmd_TokenizeString: %s\n", text_in);
#endif

	// clear previous args
	cmd_argc = 0;

	if(!text_in)
	{
		return;
	}

	Q_strncpyz(cmd_cmd, text_in, sizeof(cmd_cmd));

	text = text_in;
	textOut = cmd_tokenized;

	while(1)
	{
		if(cmd_argc == MAX_STRING_TOKENS)
		{
			return;				// this is usually something malicious
		}

		while(1)
		{
			// skip whitespace
			while(*text && *text <= ' ')
			{
				text++;
			}
			if(!*text)
			{
				return;			// all tokens parsed
			}

			// skip // comments
			if(text[0] == '/' && text[1] == '/')
			{
				return;			// all tokens parsed
			}

			// skip /* */ comments
			if(text[0] == '/' && text[1] == '*')
			{
				while(*text && (text[0] != '*' || text[1] != '/'))
				{
					text++;
				}
				if(!*text)
				{
					return;		// all tokens parsed
				}
				text += 2;
			}
			else
			{
				break;			// we are ready to parse a token
			}
		}

		// handle quoted strings
		// NOTE TTimo this doesn't handle \" escaping
		if(!ignoreQuotes && *text == '"')
		{
			cmd_argv[cmd_argc] = textOut;
			cmd_argc++;
			text++;
			while(*text && *text != '"')
			{
				*textOut++ = *text++;
			}
			*textOut++ = 0;
			if(!*text)
			{
				return;			// all tokens parsed
			}
			text++;
			continue;
		}

		// regular token
		cmd_argv[cmd_argc] = textOut;
		cmd_argc++;

		// skip until whitespace, quote, or command
		while(*text > ' ')
		{
			if(!ignoreQuotes && text[0] == '"')
			{
				break;
			}

			if(text[0] == '/' && text[1] == '/')
			{
				break;
			}

			// skip /* */ comments
			if(text[0] == '/' && text[1] == '*')
			{
				break;
			}

			*textOut++ = *text++;
		}

		*textOut++ = 0;

		if(!*text)
		{
			return;				// all tokens parsed
		}
	}

}

/*
============
Cmd_TokenizeString
============
*/
void Cmd_TokenizeString(const char *text_in)
{
	Cmd_TokenizeString2(text_in, qfalse);
}

/*
============
Cmd_TokenizeStringIgnoreQuotes
============
*/
void Cmd_TokenizeStringIgnoreQuotes(const char *text_in)
{
	Cmd_TokenizeString2(text_in, qtrue);
}

/*
============
Cmd_AddCommand
============
*/
void Cmd_AddCommand(const char *cmd_name, xcommand_t function)
{
	cmd_function_t *cmd;

	// fail if the command already exists
	for(cmd = cmd_functions; cmd; cmd = cmd->next)
	{
		if(!strcmp(cmd_name, cmd->name))
		{
			// allow completion-only commands to be silently doubled
			if(function != NULL)
			{
				Com_Printf("Cmd_AddCommand: %s already defined\n", cmd_name);
			}
			return;
		}
	}

	// use a small malloc to avoid zone fragmentation
	cmd = S_Malloc(sizeof(cmd_function_t));
	cmd->name = CopyString(cmd_name);
	cmd->function = function;
	cmd->complete = NULL;
	cmd->next = cmd_functions;
	cmd_functions = cmd;
}

/*
============
Cmd_SetCommandCompletionFunc
============
*/
void Cmd_SetCommandCompletionFunc(const char *command, completionFunc_t complete)
{
	cmd_function_t *cmd;

	for(cmd = cmd_functions; cmd; cmd = cmd->next)
	{
		if(!Q_stricmp(command, cmd->name))
		{
			cmd->complete = complete;
		}
	}
}

/*
============
Cmd_RemoveCommand
============
*/
void Cmd_RemoveCommand(const char *cmd_name)
{
	cmd_function_t *cmd, **back;

	back = &cmd_functions;
	while(1)
	{
		cmd = *back;
		if(!cmd)
		{
			// command wasn't active
			return;
		}
		if(!strcmp(cmd_name, cmd->name))
		{
			*back = cmd->next;
			if(cmd->name)
			{
				Z_Free(cmd->name);
			}
			Z_Free(cmd);
			return;
		}
		back = &cmd->next;
	}
}


/*
============
Cmd_CommandCompletion
============
*/
void Cmd_CommandCompletion(void (*callback) (const char *s))
{
	cmd_function_t *cmd;

	for(cmd = cmd_functions; cmd; cmd = cmd->next)
	{
		callback(cmd->name);
	}
}

/*
============
Cmd_CompleteArgument
============
*/
void Cmd_CompleteArgument(const char *command, char *args, int argNum)
{
	cmd_function_t *cmd;

	for(cmd = cmd_functions; cmd; cmd = cmd->next)
	{
		if(!Q_stricmp(command, cmd->name) && cmd->complete)
		{
			cmd->complete(args, argNum);
		}
	}
}


/*
============
Cmd_ExecuteString

A complete command line has been parsed, so try to execute it
============
*/
void Cmd_ExecuteString(const char *text)
{
	cmd_function_t *cmd, **prev;

	// execute the command line
	Cmd_TokenizeString(text);
	if(!Cmd_Argc())
	{
		return;					// no tokens
	}

	// check registered command functions   
	for(prev = &cmd_functions; *prev; prev = &cmd->next)
	{
		cmd = *prev;
		if(!Q_stricmp(cmd_argv[0], cmd->name))
		{
			// rearrange the links so that the command will be
			// near the head of the list next time it is used
			*prev = cmd->next;
			cmd->next = cmd_functions;
			cmd_functions = cmd;

			// perform the action
			if(!cmd->function)
			{
				// let the cgame or game handle it
				break;
			}
			else
			{
				cmd->function();
			}
			return;
		}
	}

	// check cvars
	if(Cvar_Command())
	{
		return;
	}

	// check client game commands
	if(com_cl_running && com_cl_running->integer && CL_GameCommand())
	{
		return;
	}

	// check server game commands
	if(com_sv_running && com_sv_running->integer && SV_GameCommand())
	{
		return;
	}

	// check ui commands
	if(com_cl_running && com_cl_running->integer && UI_GameCommand())
	{
		return;
	}

	// send it as a server command if we are connected
	// this will usually result in a chat message
	CL_ForwardCommandToServer(text);
}

/*
============
Cmd_List_f
============
*/
void Cmd_List_f(void)
{
	cmd_function_t *cmd;
	int             i;
	char           *match;

	if(Cmd_Argc() > 1)
	{
		match = Cmd_Argv(1);
	}
	else
	{
		match = NULL;
	}

	i = 0;
	for(cmd = cmd_functions; cmd; cmd = cmd->next)
	{
		if(match && !Com_Filter(match, cmd->name, qfalse))
			continue;

		Com_Printf("%s\n", cmd->name);
		i++;
	}
	Com_Printf("%i commands\n", i);
}

/*
==================
Cmd_CompleteCfgName
==================
*/
void Cmd_CompleteCfgName(char *args, int argNum)
{
	if(argNum == 2)
	{
		Field_CompleteFilename("", "cfg", qfalse);
	}
}

/*
============
Cmd_Init
============
*/
void Cmd_Init(void)
{
	Cmd_AddCommand("cmdlist", Cmd_List_f);
	Cmd_AddCommand("exec", Cmd_Exec_f);
	Cmd_SetCommandCompletionFunc("exec", Cmd_CompleteCfgName);
	Cmd_AddCommand("vstr", Cmd_Vstr_f);
	Cmd_SetCommandCompletionFunc("vstr", Cvar_CompleteCvarName);
	Cmd_AddCommand("echo", Cmd_Echo_f);
	Cmd_AddCommand("wait", Cmd_Wait_f);
}
